﻿using System;
using System.Threading;

namespace Tools
{
    /// <summary>
    /// 来自 http://blogs.msdn.com/b/ploeh/archive/2007/02/09/agenericiasyncresultimplementation.aspx
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class AsyncResult<T> : IAsyncResult, IDisposable
    {
        /*
When implementing the Async Pattern, you can create and return an instance of AsyncResult<T> and return it from your BeginX method. 
Then, when your asynchronous operation completes, call the Complete method, which will store the result, release the WaitHandle and invoke any callback supplied by the client.
To implement the EndX method, cast the incoming IAsyncResult to AsyncResult<T>, call WaitOne on its AsyncWaitHandle and return its Result property
         */
        private readonly AsyncCallback callback_;
        private bool completed_;
        private bool completedSynchronously_;
        private readonly object asyncState_;
        private readonly ManualResetEvent waitHandle_;
        private T result_;
        private Exception e_;
        private readonly object syncRoot_;

        public AsyncResult(AsyncCallback cb, object state)
            : this(cb, state, false)
        { }

        public AsyncResult(AsyncCallback cb, object state, bool completed)
        {
            this.callback_ = cb;
            this.asyncState_ = state;
            this.completed_ = completed;
            this.completedSynchronously_ = completed;
            this.waitHandle_ = new ManualResetEvent(false);
            this.syncRoot_ = new object();
        }

        #region IAsyncResult Members

        public object AsyncState
        {
            get { return this.asyncState_; }
        }

        public WaitHandle AsyncWaitHandle
        {
            get { return this.waitHandle_; }
        }

        public bool CompletedSynchronously
        {
            get
            {
                lock (this.syncRoot_)
                {
                    return this.completedSynchronously_;
                }
            }
        }

        public bool IsCompleted
        {
            get
            { lock (this.syncRoot_) { return this.completed_; } }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                lock (this.syncRoot_)
                {
                    if (this.waitHandle_ != null)
                    {
                        ((IDisposable)this.waitHandle_).Dispose();
                    }
                }
            }
        }

        public Exception Exception
        {
            get
            {
                lock (this.syncRoot_) { return this.e_; }
            }
        }

        public T Result
        {
            get
            {
                lock (this.syncRoot_) { return this.result_; }
            }
        }

        public void Complete(T result, bool completedSynchronously)
        {
            lock (this.syncRoot_)
            {
                this.completed_ = true;
                this.completedSynchronously_ = completedSynchronously;
                this.result_ = result;
            }
            this.SignalCompletion();
        }

        public void HandleException(Exception e, bool completedSynchronously)
        {
            lock (this.syncRoot_)
            {
                this.completed_ = true;
                this.completedSynchronously_ = completedSynchronously;
                this.e_ = e;
            }
            this.SignalCompletion();
        }

        private void SignalCompletion()
        {
            this.waitHandle_.Set();
            ThreadPool.QueueUserWorkItem(new WaitCallback(this.InvokeCallback));
        }

        private void InvokeCallback(object state)
        {
            if (this.callback_ != null)
            {
                this.callback_(this);
            }
        }
    }
}